package com.demo.controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author gy
 * @date 2022/6/18
 */
@RestController
public class RedissionController {

    private static final Logger logger = LoggerFactory.getLogger(RedissionController.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private Redisson redisson;

    @RequestMapping("/deduct")
    public String deductStock() {

        String lockKey = "product_001";

        RLock redissonLock = redisson.getLock(lockKey);
        // 给该线程加唯一标识
        // uuid 重复的概率，完全可以忽略不记
        String clientId = UUID.randomUUID().toString();

        try {
            /**
             * redis 的 分布式锁，通过把 redis的key当锁
              */
//            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "stock");// jedis.setnx(key,value)
            // 如果 加 这把锁不能成功，说明已经有其他线程在使用这把锁，该线程只能等待，被阻塞；
            // 在 业务代码中，可以给出温馨提示

            /**
             * 设置过期时间解决 redis宕机，key永久失效的问题
             * 细节：和上一行代码分开写，违背了原子性操作，若在两行代码之间出问题，程序照样 GG
             */
//            stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS);

//             一定要注意原子性操作
//            Boolean result = stringRedisTemplate.opsForValue().
//                    setIfAbsent(lockKey, clientId,1,TimeUnit.SECONDS);// jedis.setnx(key,value)

//            if(!result){
//                String client = stringRedisTemplate.opsForValue().get(lockKey);
//                logger.info( "另一个实例:" + client +" 的业务还未处理完，本实例暂时无法进行业务操作");
//                return "error";
//            }

            // 获取到这个锁对象
            redissonLock.lock(2,TimeUnit.SECONDS);



//            /**
//             * 单体部署的加锁方式
//             */
//            //说明：关键代码，需要有原子性，需要已原子块的方式执行，不然高并发场景下会出现问题，出现库存超卖
//            synchronized (this) {
                int stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
                if (stock > 0){
                    int realStock = stock - 1;
                    stringRedisTemplate.opsForValue().set("stock",realStock + ""); // jedis.set(key,value)

                    // 模拟业务执行时间 1s
                    Thread.sleep(50);

                    logger.info("扣减成功，剩余库存：" + realStock + "");
                } else {
                    logger.info("扣减失败，库存不足");
                }
//            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {

            redissonLock.unlock();

            // 一定要释放锁，这把锁就会在 redis永久存放，影响其他应用实例的业务处理
//            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
//                stringRedisTemplate.delete(lockKey);
//            }
        }

        return "end";
    }


}
